如何使用Guice创建递归对象图?

时间:2011-09-14 13:20:24

标签: java guice

假设我有5个类A,B,C,D,E,它们都实现了一个公共接口X.每个类B,C,D和E都有一个X类型的字段(所以它们可以看作是包装器类)。

创建的实例是在运行时确定的,因此我可以使用以下对象图之一:

E -> D -> C -> B -> A
D -> B -> A
E -> A
A

(订单是固定的,最里面的实例总是A型,但是没有限制。)

不,我想用Guice创建这个对象,以避免手工提供所有其他依赖项。

最简单的方法是什么?目前似乎我必须

  1. 让Guice创建A
  2. 的实例
  3. 创建一个将X.class绑定到此实例的模块和一个带有此附加模块的子注入器
  4. 让Guice创建下一个实例(例如,类型B)
  5. 重复2.,现在将X.class绑定到B
  6. 的实例
  7. 重复3.和4.直到创建所有对象
  8. 有更简单的方法吗?我是否可能以某种方式在每次创建时自动注册X的子类的新实例作为X的绑定?

    编辑:澄清“在运行时确定创建了哪些实例”:

    我目前的代码如下:

    X createX() {
      X x = new A(dependencies);
      if (useB) x = new B(x, some, dependencies);
      if (useC) x = new C(x, further, dependencies);
      if (useD) x = new D(x, dependency);
      if (useE) x = new E(x, yet, other, dependencies);
      return x;
    }
    

    标志useB,useC,useD和useE的值来自属性文件。

    我的主要目标是手动保存为构造函数提供所有依赖项。

    编辑:解决方案:

    我添加了自己的解决方案,我在此期间找到了解决方案。感谢所有的回答者!

    改进我的解决方案的一种方法是可以删除构造函数参数上的@InInstance注释。我已经尝试过类型监听器,但我还没有找到办法。欢迎提示。

3 个答案:

答案 0 :(得分:4)

我已经针对这个问题开发了以下解决方案:

首先,我创建了一个标记注释@InInstance,它将Class个对象作为值。我使用这个注释来注释所有包装类中的类型X的参数(即B到E)。例如:

class B {
  B(@InInstance(B.class) X x,
    Other dependencies) {
  ...
  }
}

现在,如果我想要一个完整链的实例(A到E),我绑定

  • X.class到E.class
  • X.class annotatedWith InInstance(E.class)to D.class
  • ...
  • X.class annotatedWith InInstance(B.class)to A.class

然后拨打createInstance(X.class)

这解决了让Guice创建我的实例并提供依赖关系的问题。

现在针对根据属性文件在运行时创建适当绑定的问题,我创建了一个Guice扩展,允许我创建“包装器绑定”。这样的绑定需要一个类型(这里是X),一个包装器实现类型(这里是B到E之一)和一个基本模块,它需要包含一个X的绑定。然后它创建一个包含两个绑定的新模块:

  • 与基本模块中的X相同的绑定,但是annotatedWith(InInstance(wrapper type))
  • 从X到包装类型的绑定

然后可以使用这样的模块来覆盖基础模块与Modules.override(base module).with(wrapper module)的绑定。

使用一些不错的Guice风格的EDSL,这看起来像:

Module baseModule = ... // contains binding for X to A

if (useB) {
  Module wrapperModule = new ChainingModule() {
    void configure() {
      wrap(X.class).from(baseModule).with(B.class); // possible to use .in(...) etc. here
      // In this case, this line is equivalent to

      // bind(X.class).annotatedWith(InInstance(B.class)).to(A.class);
      // bind(X.class).to(B.class);

      // It has the advantage of automatically looking up the current binding
      // for X in the base module, which makes it easy to chain this.
    }
  }
  baseModule = Modules.override(baseModule).with(wrapperModule);
}

...

对于常见情况的一些辅助方法,它看起来像:

Module module = ... // contains binding for X to A
if (useB) {
  module = Chaining.wrap(X.class).from(module).with(B.class);
}
if (useC) {
  module = Chaining.wrap(X.class).from(module).with(C.class);
}
if (useD) {
  module = Chaining.wrap(X.class).from(module).with(D.class);
}
if (useE) {
  module = Chaining.wrap(X.class).from(module).with(E.class);
}

Injector injector = Guice.createInjector(module);
X x = injector.createInstance(X.class);

每一行创建适当的包装器模块并返回一个包含给定模块的新模块,其中一些绑定由包装器模块覆盖。

答案 1 :(得分:1)

"在运行时"增加了相当多的复杂性。我认为这意味着您创建的每个 X实例,您可以对包裹A的对象链做出一些决定。

如果你不将X注入E,D,C& B,施工期间?而是添加setNext(X)方法。我不确定这对你有多现实,但请听我说。您可以创建如下工厂:

class XFactory {
  @Inject Provider<A> a;
  @Inject Provider<B> b;
  @Inject Provider<C> c;
  @Inject Provider<D> d;
  @Inject Provider<E> e;
  public X create(Data state) {
    // the 'state' object is just whatever information you use to 
    // decide what objects you need to create;
    X result = a.get();
    if(state.useB()) {
      B head = b.get();
      head.setNext(result);
      result = head;
    }
    if(state.useC()) {
      C head = c.get();
      head.setNext(result);
      result = head;
    }
    if(state.useD()) {
      D head = d.get();
      head.setNext(result);
      result = head;
    }
    if(state.useE()) {
      E head = e.get();
      head.setNext(result);
      result = head;
    }
    return result;
  }
}

您可以考虑引入DelegatingX或WrapperX等接口来保存setNext()方法。它可能会减少一些重复。

另外,如果&#34;在运行时&#34;实际上是应用程序启动,因此X的每个实例应该是同一个对象链,然后你有一个额外的选项:

class MyModule extends AbstractModule {
  public void configure() {
    Multibinder<DelegatingX> chain
        = Multibinder.newSetBinder(binder(), DelegatingX.class);
    if(useB()) chain.addBinding().to(B.class);
    if(useC()) chain.addBinding().to(C.class);
    if(useD()) chain.addBinding().to(D.class);
    if(useE()) chain.addBinding().to(E.class);
  }
  @Provides X provideX(Set<DelegatingX> chain, A a) {
    X result = a;
    // 'item' is B, then C...
    for(X item : chain) {
      item.setNext(result);
      result = item;
    }
    return result; // this is E
  }
}

我知道这个答案非常抽象,但我希望它能让你更接近解决方案。

答案 2 :(得分:1)

我还发现你的定义“运行时”部分不清楚。

但是,我们假设我们首先想象的是如何在没有Guice的情况下制作这些链条。

class chainHolder {
  public final X chain1 = new E(new D(new C(new B(new A()))));
  public final X chain2 = new D(new B(new A()));
  public final X chain3 = new E(new  A());
  public final X chain4 = new A();
}

但是你的问题是,你想要注入的每个构造函数中还有很多其他东西要传递给你吗?好吧,让我们为这些设置一些注入:

//Pseudo multi-class def here
classes B...E extends X {
  private final X nextInChain;
  @Inject
  public B...E(TheStuffINeedToo NotMentionedAbove, X nextInChain) {
    super(TheStuffINeedToo);
    this.nextInChain = nextInChain;
  }
}

class A extends X {
  @Inject
  public A(TheStuffINeedToo NotMentionedAbove) {
    super(TheStuffINeedToo);
  }
}

class chainHolder {
  public final X chain1;
  public final X chain2;
  public final X chain3;
  public final X chain4;
  @Inject
  public chainHolder(@Chain1 X c1, @Chain2 X c2, @Chain3 X c3, @Chain4 X c4) {
    chain1 = c1;
    chain2 = c2;
    chain3 = c3;
    chain4 = c4;
  }
}

class chainModlule extends AbstractModule {
  public void configure() {
    //The other stuff you wanted provided gets bound here.
    bind(TheStuffINeedToo.class).to(TheStuffNotMentionedInTheQuestion.class);
    //Maybe A should always be the same terminating instance.
    bind(A).in(Singleton.class);
    bind(X).annotatedWith(Chain4.class).to(A.class);
  }

  @Provides @Chain1
  public X providesChain1X(@Chain1 Provider<E> e) {
    return e.get();
  }

  @Provides @Chain1
  public E providesChain1E(@Chain1 Provider<D> d, TheStuffINeedToo stuff) {
    return new E(stuff, d.get());
  }

  @Provides @Chain1
  public D providesChain1D(@Chain1 Provider<C> c, TheStuffINeedToo stuff) {
    return new D(stuff, c.get());
  }

  @Provides @Chain1
  public C providesChain1C(@Chain1 Provider<B> b, TheStuffINeedToo stuff) {
    return new C(stuff, b.get());
  }

  @Provides @Chain1
  public B providesChain1B(Provider<A> a, TheStuffINeedToo stuff) {
    return new B(stuff, a.get());
  }

 // ... Similarly for chain2 then ...

  @Provides @Chain3
  public X providesChain3X(@Chain3 Provider<E> e) {
    return e.get();
  }

  @Provides @Chain3
  public E providesChain3E(Provider<A> a, TheStuffINeedToo stuff) {
    return new E(stuff, a.get());
  }
}

现在。你看,这不是保存/很多/工作。如果所有类都接受了你希望注入为你做的相同的东西(比如我的伪设置),那么你可以将整个链转移到一个提供者并重用那个东西实例,或者提供东西。