2012-03-30 25 views
20

这是我的问题:自定义Guice Scope还是更好的方法?

知道我正在写模拟是首要的重要。这是一个独立的应用程序,并且是单线程的。我基本上有两类具有不同作用域要求的对象。

  1. 应在整个模拟过程中用作单例的类。作为一个例子,一个Random的实例。

  2. 在一起创建的组以及组内的每个实例都应该像Singleton一样对待。例如,RootObject是最高级别,并且依赖于ClassAClassB,这两者都对ClassD有依赖关系。对于任何给定RootObject,它的两个依赖关系(ClassAClassB)都应该取决于ClassD的相同实例。但是,ClassD的实例不应该在RootObject的不同实例之间共享。

希望这是有道理的。我可以想到两种方法。一种是将所有注入的对象标记为Singletons,创建根注入器,并在每次需要创建新的RootObject实例时关闭子注入器。然后,将RootObject及其所有依赖项的实例创建为单例,但下一次创建另一个RootObject时,该范围信息将被丢弃。

第二种方法是实现某种类型的自定义作用域。

Guice的文档给出了相互矛盾的建议......一方面它说你应该有一个单独的注入器,理想情况下它被调用一次来创建一些顶级类。另一方面,它说远离自定义范围。

+0

我有一个想法,@Assisted注射可以在这里使用,但我无法确切地看到它应该如何使用... – Rich 2012-03-30 12:42:44

+0

@Josh:哪里的实况说远离示波器? – 2012-07-30 07:23:33

+0

@ A.H。,http://code.google.com/p/google-guice/wiki/CustomScopes - 第一行。 “通常建议用户不要编写自己的自定义作用域 - 内置作用域应该足以满足大多数应用程序的需求。”我发现第一种方法很好 - 根据需要创建儿童注射器。我们只需要小心,“每个组的单身人士”不会意外地在父注射器中受到束缚,但这很容易检查。 – Josh 2012-07-31 17:39:00

回答

0

请问为什么你需要单身?

我不推荐创建自定义范围。混合范围的最好和最简单的方法是注入提供者而不是对象。通过提供者,您可以从业务代码逻辑中控制对象的范围。

有关详细信息,请参阅此Guice documentation

+0

我不认为这适用。如果我将请求范围对象与单例混合在一起,那么确定...这会很好。但是,我需要将单身人士与不存在的范围混合在一起。看起来我们最简单的方法就是使用儿童注射器。 – Josh 2012-04-09 21:06:15

+0

为了解决你的Singleton问题 - 我们有两个级别的有状态对象 - 其实例需要在整个模拟中共享的状态(如随机数生成器),以及其他实例需要在模拟。动态地创建不同的子组件,并且每个子组件需要它自己的这个每个组件单例的实例;然而,该子组件内的其他对象需要访问相同的实例。 – Josh 2012-04-09 21:11:26

3

您是否考虑过使用提供者?这将是很容易写一个符合你的要求,如:

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, ...); 
    } 
} 

您可以使用提供两种方式:

  1. 绑定它作为一个供应商要么@Provides接口或.toProvider()结合装饰。
  2. 直接注入提供程序并调用它以根据需要创建RootObject实例。

希望这可以帮助。

+4

嗯......用'new'创建实例是DI框架中的原罪。特别是如果这些类的_any_想要注入更多的东西。 – 2012-07-30 07:26:48

11

在我看来,你需要为每个RootObject及其所有依赖项的实例设置一个范围。

在吉斯您可以创建一个自定义的范围,说@ObjectScoped,像这样:

@Target({ TYPE, METHOD }) 
@Retention(RUNTIME) 
@ScopeAnnotation 
public @interface ObjectScoped {} 

现在只需将RootObjectABD到这个范围:

@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); 
    } 

} 

整合这些示波器与吉斯,您将需要一个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; 
+0

我不知道在创建实例后是否留下了一个范围。某种提供程序依赖项的懒惰实例化可能会失败... – BrunoJCM 2014-01-04 23:28:59

4

稍加设置,吉斯可以提供两层没有自定义范围的范围。外部是@Singleton,内部是@RequestScoped,由servlet扩展提供。即使您正在讨论的不是Java EE servlet容器,也适用。

有一个单一的根级别注入器来处理你的单身人士。一定要在你的root级别的模块来声明请求范围注释像这样:

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在新的请求范围内运行匿名CallableCallable然后创建一个子注入器并为任何每个请求的种子对象添加一个新的绑定。

种子是东西需要但不能由Guice单独创建的对象,例如请求或迭代标识。作为scopeRequest的第二个参数传递的新HashMap是另一种将种子直接插入新范围的方法。无论如何,我更喜欢子模块方式,因为bindings for the seeded values are always required

子注射器然后“在”请求范围内,并可用于提供@RequestScoped的东西。

见这也:How to use ServletScopes.scopeRequest() and ServletScopes.continueRequest()?

相关问题