自定义Guice范围,还是更好的方法?

时间:2012-03-30 12:19:04

标签: design-patterns scope guice

这是我的问题:

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

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

  2. 一起创建的类组以及组内的每个实例都应该被视为Singleton。例如,假设RootObject是顶级类,并且依赖于ClassAClassB,这两者都依赖于ClassD。对于任何给定的RootObject,它的两个依赖项(ClassAClassB)都应该依赖于ClassD的同一个实例。但是,不应在ClassD的不同实例之间共享RootObject的实例。

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

    第二种方法是实现某种类型的自定义范围。

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

4 个答案:

答案 0 :(得分:12)

在我看来,您需要RootObject的每个实例及其所有依赖项的范围。

在Guice中,您可以创建自定义范围,例如@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);
    }

}

要将这些范围与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, ...);
    }
}

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

  1. 使用@Provides界面或.toProvider()绑定装饰将其绑定为提供商。
  2. 直接注入提供程序并根据需要调用它以创建RootObject实例。
  3. 希望这会有所帮助。

答案 3 :(得分:0)

我可以问你为什么需要单身人士?

我不建议创建自定义范围。混合范围的最佳和最简单的方法是注入提供者而不是对象。使用提供程序,您可以从业务代码逻辑控制对象的范围。

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