Guice Custom Scopes

Guice官方并不建议自定义scopes,它认为自带的scopes已经足够使用。

创建custom scopes需要以下几步:

  1. 定义一个scoping annotation。
  2. 实现Scope接口。
  3. 将scope annotation绑定到它的实现。
  4. 触发scope的开始以及结束。

Defining a scoping annotation

Scoping annotation用来标识自定义的scope,可以用它来注解Guice构造生成的类型、@Provides方法以及用在in()从句用于声明绑定。

1
2
3
4
5
6
7
8
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 {}

Implement Scope

Scope接口可以保证在每个scope实例中每种类型最多只有一个实例。官方提供了一个实现叫做SimpleScope,我们可以直接使用,也可以根据需要对SimpleScope进行调整生成自己的Scope实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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.&lt;KeyClass&gt;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;
}
}

Binding the annotation to the implementation

我们必须将scoping annotation绑定到对应的scope implementation上,这个在module的configure()方法中配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BatchScopeModule extends AbstractModule {
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需要手动进入和退出。需要注意:调用exit()方法需要在finally从句中,否则如果抛出了异常,scope将一直处于open状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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();
}
}