简体   繁体   English

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

[英]How to create recursive object graphs with Guice?

Suppose I have 5 classes A, B, C, D, E that all implement a common interface X. Each of the classes B, C, D and E has a field of type X (so they can be seen as wrapper classes). 假设我有5个类A,B,C,D,E,它们都实现了一个公共接口X.B,C,D和E类中的每一个都有一个X类型的字段(所以它们可以看作是包装类)。

Which instances are created is determined at runtime, so I could have for example one of the following object graphs: 创建哪些实例是在运行时确定的,因此我可以使用以下对象图之一:

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

(The order is fixed and the innermost instance is always of type A, but otherwise there are no restrictions.) (订单是固定的,最里面的实例总是类型A,但是没有限制。)

No I'd like to create this objects with Guice to avoid providing all their other dependencies by hand. 不,我想用Guice创建这个对象,以避免手工提供所有其他依赖项。

What's the easiest way to do this? 最简单的方法是什么? Currently it seems that I have to 目前似乎我必须这样做

  1. let Guice create instance of A 让Guice创建A的实例
  2. create a module that binds X.class to this instance and a child injector with this additional module 创建一个模块,将X.class绑定到此实例,并使用此附加模块创建子注入器
  3. let Guice create the next instance (eg, of type B) 让Guice创建下一个实例(例如,类型B)
  4. repeat 2., now binding X.class to the instance of B 重复2.,现在将X.class绑定到B的实例
  5. repeat 3. and 4. until all objects are created 重复3.和4.直到创建所有对象

Is there an easier way? 有没有更简单的方法? Could I perhaps somehow automatically register a new instance of a subclass of X as a binding for X each time it is created? 我是否可能以某种方式自动注册X的子类的新实例作为每次创建X的绑定?

Edit: Clarification of " Which instances are created is determined at runtime ": 编辑:澄清“ 在运行时确定创建了哪些实例 ”:

My current code looks like this: 我当前的代码如下所示:

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

The value of the flags useB, useC, useD, and useE comes from a properties file. 标志useB,useC,useD和useE的值来自属性文件。

My main goal is to save providing all the dependencies to the constructors manually. 我的主要目标是手动保存为构造函数提供所有依赖项。

Edit: Solution: 编辑:解决方案:

I have added my own solution which I have found in the meantime. 我添加了自己的解决方案,我在此期间找到了解决方案。 Thanks to all answerers! 感谢所有的回答者!

A way to improve my solution would be to make it possible to remove the @InInstance annotation on the constructor parameters. 改进我的解决方案的一种方法是可以删除构造函数参数上的@InInstance注释。 I have experimented with type listeners, but I haven't found a way to do this. 我已经尝试过类型监听器,但我还没有找到办法。 Hints would be welcome. 提示是受欢迎的。

I have developed the following solution to the problem: 我已经为这个问题开发了以下解决方案:

First, I create a marker annotation @InInstance , which takes a Class object as value. 首先,我创建一个标记注释@InInstance ,它将Class对象作为值。 I use this annotation to annotate the parameters of type X in all wrapper classes (ie, B to E). 我使用这个注释来注释所有包装类中的类型X的参数(即B到E)。 Example: 例:

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

Now if I want for example an instance of the full chain (A to E), I bind 现在,如果我想要一个完整链的实例(A到E),我绑定

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

and then call createInstance(X.class) . 然后调用createInstance(X.class)

This solves the problem of letting Guice create my instances and provide the dependencies. 这解决了让Guice创建我的实例并提供依赖关系的问题。

Now for the problem of creating the appropriate bindings at runtime according to the properties file, I created a Guice extension that allows me to create "wrapper bindings". 现在针对根据属性文件在运行时创建适当绑定的问题,我创建了一个Guice扩展,允许我创建“包装器绑定”。 Such a binding takes a type (here X), a wrapper implementation type (here one of B to E) and a base module, which needs to contain a binding for X. It then creates a new module that contains two bindings: 这样的绑定需要一个类型(这里是X),一个包装器实现类型(这里是B到E之一)和一个基本模块,它需要包含X的绑定。然后它创建一个包含两个绑定的新模块:

  • the same binding for X as in the base module, but annotatedWith(InInstance(wrapper type)) 与基本模块中的X相同的绑定,但是带annotatedWith(InInstance(wrapper type))
  • a binding from X to the wrapper type 从X到包装器类型的绑定

Such a module can then be used to override the bindings of the base module with Modules.override(base module).with(wrapper module) . 然后可以使用这样的模块来覆盖基本模块与Modules.override(base module).with(wrapper module)的绑定。

With some nice Guice-style EDSL, this looks like: 使用一些不错的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);
}

...

And with some helper methods for common cases it looks like: 对于常见情况,有一些辅助方法,它看起来像:

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

Each line creates the appropriate wrapper module and returns a new module containing the given module with some bindings overriden by the wrapper module. 每行创建适当的包装器模块并返回包含给定模块的新模块,其中一些绑定由包装器模块覆盖。

"At Runtime" adds quite a bit of complexity. “在运行时”增加了相当多的复杂性。 I assume that means for each X instance you create, you make some decision about the chain of objects wrapping A. 我假设这意味着你创建的每个 X实例,你对包装A的对象链做出一些决定。

How about if you don't inject X into E, D, C & B, during construction? 如果你在施工期间没有将X注入E,D,C和B怎么样? Instead, add setNext(X) method. 而是添加setNext(X)方法。 I'm not sure how realistic that is for you, but hear me out. 我不确定这对你有多现实,但是听我说。 You could create a factory like the following: 您可以创建如下工厂:

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

You could consider introducing an interface like DelegatingX or WrapperX to hold the setNext() method. 你可以考虑引入一个像DelegatingX或WrapperX这样的接口来保存setNext()方法。 It might reduce some of the repetition. 它可能会减少一些重复。

Also, if "at runtime" is actually application boot, and therefore each instance of X should be the same chain of objects, then you have an additional option: 此外,如果“在运行时”实际上是应用程序启动,因此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
  }
}

I know this answer is pretty abstract, but I hope it gets you a little bit closer to a solution. 我知道这个答案非常抽象,但我希望它能让你更接近解决方案。

I also find the "at runtime" part of your definition unclear. 我还发现你的定义中的“运行时”部分不清楚。

But let's say we start by imagining how these chains are made without Guice. 但是,让我们假设我们首先想象这些链是如何在没有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();
}

But your problem is that there's a lot of other stuff to pass into each constructor that you'd like to have injected? 但是你的问题是,你想要注入的每个构造函数中还有很多其他东西要传递给你吗? Okay let's setup some injection for these instead: 好的,让我们为这些设置一些注入:

//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());
  }
}

Now. 现在。 You see that's not saving /a lot/ of work. 你看,这不是保存/很多/工作。 If all the classes take in the same stuff you wanted injection to do for you (like my pseudo setup does) then you could roll the whole chain into one provider and reuse that stuff instance, or a provider for the stuff. 如果所有的类都采用了你想要注入的相同的东西(比如我的伪设置),那么你可以将整个链转移到一个提供者并重用那个东西实例,或者提供东西。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM