这是我的问题:
首先要知道我正在编写模拟。这是一个独立的应用程序,是单线程的。我基本上有两类具有不同范围要求的对象。
在整个模拟过程中应该用作单例的类。以Random为例,作为一个例子。
一起创建的类组以及组内的每个实例都应该被视为Singleton。例如,假设RootObject
是顶级类,并且依赖于ClassA
和ClassB
,这两者都依赖于ClassD
。对于任何给定的RootObject
,它的两个依赖项(ClassA
和ClassB
)都应该依赖于ClassD
的同一个实例。但是,不应在ClassD
的不同实例之间共享RootObject
的实例。
希望这是有道理的。我可以想到两种方法。一种是将所有注入的对象标记为单例,创建根注入器,并在每次需要创建新的RootObject
实例时分离子注入器。然后,RootObject
及其所有依赖项的实例将创建为单例,但下次我创建另一个RootObject
时,该范围信息将被丢弃。
第二种方法是实现某种类型的自定义范围。
Guice文档给出了相互矛盾的建议......一方面,它说你应该有一个注入器,理想情况下它被调用一次来创建一些顶级类。另一方面,它表示要远离自定义范围。
答案 0 :(得分:12)
在我看来,您需要RootObject
的每个实例及其所有依赖项的范围。
在Guice中,您可以创建自定义范围,例如@ObjectScoped
,如下所示:
@Target({ TYPE, METHOD })
@Retention(RUNTIME)
@ScopeAnnotation
public @interface ObjectScoped {}
现在只需将RootObject
,A
,B
和D
放入此范围:
@ObjectScoped
public class RootObject {
private A a;
private B b;
@Inject
public RootObject(A a, B b) {
this.a = a;
this.b = b;
}
public A getA() {
return a;
}
public B getB() {
return b;
}
}
@ObjectScoped
public class A {
private D d;
@Inject
public A(D d) {
this.d = d;
}
public D getD() {
return d;
}
}
// The same for B and D
现在每个RootObject
都有自己的范围。您可以将其实现为简单的HashMap
:
public class ObjectScope {
private Map<Key<?>,Object> store = new HashMap<Key<?>,Object>();
@SuppressWarnings("unchecked")
public <T> T get(Key<T> key) {
return (T)store.get(key);
}
public <T> void set(Key<T> key, T instance) {
store.put(key, instance);
}
}
要将这些范围与Guice集成,您需要com.google.inject.Scope
- 实现,这样您就可以在Module
中切换范围和相应的连线。
public class GuiceObjectScope implements Scope {
// Make this a ThreadLocal for multithreading.
private ObjectScope current = null;
@Override
public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
return new Provider<T>() {
@Override
public T get() {
// Lookup instance
T instance = current.get(key);
if (instance==null) {
// Create instance
instance = unscoped.get();
current.set(key, instance);
}
return instance;
}
};
}
public void enter(ObjectScope scope) {
current = scope;
}
public void leave() {
current = null;
}
}
public class ExampleModule extends AbstractModule {
private GuiceObjectScope objectScope = new GuiceObjectScope();
@Override
protected void configure() {
bindScope(ObjectScoped.class, objectScope);
// your bindings
}
public GuiceObjectScope getObjectScope() {
return objectScope;
}
}
像这样初始化你的程序:
ExampleModule module = new ExampleModule();
Injector injector = Guice.createInjector(module);
GuiceObjectScope objectScope = module.getObjectScope();
创建RootObject
的第一个实例及其对应的范围:
ObjectScope obj1 = new ObjectScope();
objectScope.enter(obj1);
RootObject rootObject1 = injector.getInstance(RootObject.class);
objectScope.leave();
只需切换第二组对象的范围:
ObjectScope obj2 = new ObjectScope();
objectScope.enter(obj2);
RootObject rootObject2 = injector.getInstance(RootObject.class);
objectScope.leave();
测试您的要求是否得到满足:
assert rootObject1 != rootObject2;
assert rootObject1.getA() != rootObject2.getA();
assert rootObject1.getA().getD() == rootObject1.getB().getD();
assert rootObject1.getA().getD() != rootObject2.getB().getD();
要处理一组对象,只需输入其范围并使用注入器:
objectScope.enter(obj1);
B b1 = injector.getInstance(B.class);
objectScope.leave();
assert rootObject1.getB() == b1;
答案 1 :(得分:5)
通过一些设置,Guice可以提供没有自定义范围的双层范围。外部是@Singleton
,内部是@RequestScoped
,由servlet
扩展名提供。即使您正在讨论Java EE servlet容器以外的其他内容,这也可以工作。
有一个单根根级注射器来处理你的单身人士。请务必在根级别模块中声明请求范围注释:
public class RootModule extends AbstractModule {
@Override
protected void configure() {
// Tell guice about the request scope, so that we can use @RequestScoped
bindScope(RequestScoped.class, ServletScopes.REQUEST);
}
}
如果要输入子范围,请执行以下操作:
private void scopeAndInject(final Object perRequestSeed) {
try {
ServletScopes.scopeRequest(new Callable<Void>() {
public Void call() {
Injector requestScoped = getRootInjector().createChildInjector(
new AbstractModule() {
@Override
protected void configure() {
bind(Object.class).toInstance(perRequestSeed);
}
}
);
requestScoped.get(Something.class);
return null;
}
}, new HashMap<Key<?>, Object>()).call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
我们在这里做的是使用ServletScopes.scopeRequest
在新请求范围内运行匿名Callable
。然后Callable
创建子注入器并为每个请求种子对象添加新绑定。
种子是@RequestScoped
事物需要但只能由Guice创建的对象,如请求或迭代ID。作为HashMap
的第二个参数传递的新scopeRequest
是另一种将种子插入新范围的方法。我更喜欢子模块方式,因为bindings for the seeded values are always required无论如何。
子注入器然后“在”请求范围内,可用于提供@RequestScoped
件事。
另见:How to use ServletScopes.scopeRequest() and ServletScopes.continueRequest()?
答案 2 :(得分:2)
您是否考虑过使用提供商?编写符合您要求的文件很容易,例如:
import com.google.inject.Provider
class RootObjectProvider implements Provider<RootObject> {
...
@Override
RootObject get() {
ClassD d = new ClassD( .... );
ClassB b = new ClassB( ..., d, ...);
ClassC c = new ClassC( ..., d, ...); // Note that b and c share d.
return new RootObject(b, c, ...);
}
}
您可以通过两种方式使用提供商:
@Provides
界面或.toProvider()
绑定装饰将其绑定为提供商。RootObject
实例。希望这会有所帮助。
答案 3 :(得分:0)
我可以问你为什么需要单身人士?
我不建议创建自定义范围。混合范围的最佳和最简单的方法是注入提供者而不是对象。使用提供程序,您可以从业务代码逻辑控制对象的范围。
有关详细信息,请参阅此Guice documentation。