如何使用Guice @Inject到现有对象层次结构中? [英] How to @Inject into existing object hierarchy using Guice?

查看:59
本文介绍了如何使用Guice @Inject到现有对象层次结构中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个现有的对象层次结构,其中某些对象具有需要注入的字段。另外,还有一些其他对象是使用 Google Guice 构造的,需要注入对先前描述的对象层次结构中某些对象的引用。我该如何用Guice进行这种注入?

I have an existing object hierarchy where some objects have fields that need to be injected. Also there are some other objects that are constructed using Google Guice and need to be injected with references to some objects from previously described object hierarchy. How do I do such kind of injection with Guice?

问题是现有层次结构中的对象不是使用Guice构造的,因此默认情况下不受注入过程的约束。当然,有 injector.injectMembers()方法可以注入到现有的对象实例中,但不适用于对象层次结构。

The problem is that objects from existing hierarchy were not constructed using Guice, and therefore are not subject to inject process by default. There is, of course injector.injectMembers() method that is able to inject into existing object instance, but it does not work on object hierarchies.

对于那些想知道为什么我不能使用Guice构建提到的对象层次结构的人。此层次结构表示GUI对象,并由GUI框架( Apache Pivot )通过声明性的GUI描述(实际上是此过程可以描述为对象反序列化)。这样的接口构造非常简单,我只想将某些服务引用注入接口对象中,反之亦然(对于回调)。

For those wondering why I can't build mentioned object hierarchy using Guice. This hierarchy represents GUI objects and is built by a GUI framework (Apache Pivot) from a declarative GUI description (in fact this process can be described as object deserialization). That way interface construction is rather simple, and I only want to inject certain service references into interface objects and vice versa (for callbacks).

我当前要采用的方法如下所述。

Approach I am currently about to take is described below.

要注入到预先存在的对象层次结构中,只需让所有对注入感兴趣的对象实现某些接口,例如:

For injecting into preexisting object hierarchy just let all objects that are interested in injection implement certain interface, like:

public interface Injectable {
  void injectAll(Injector injector);
}

这些对象随后将实现此接口,如下所示:

Those objects would then implement this interface like so:

public void injectAll(Injector injector) {
  injector.injectMembers(this);
  for (Injectable child : children)
    child.injectAll(injector);
}

然后我只调用 mainWindow.injectAll(

Then I'd just call mainWindow.injectAll(injector) for root object in hierarchy and all objects of interest are injected.

不是很好的解决方案,但是可以单方面完成工作。另一方面,我需要从该层次结构中注入对象。我想可以通过为此类对象实现自定义提供程序来实现。

Not very nice solution, but gets the work done on one side. On the other side, I need to inject objects from this hierarchy. I guess it can be done via implementing custom provider for such objects.

有没有更好的解决方案来解决我的问题?也许我的方法还存在问题?

Is there a better solution to my problem? Maybe there is also something wrong with my approach?

推荐答案

此解决方案可以工作,但我想提出一个略有不同的建议

This solution will work, but I'd like to propose a slightly different one to you.

具体来说,由于您要遍历一个深层的对象结构,因此对于访问者模式而言,这确实像是一项工作。另外,您所描述的似乎需要两阶段注入器:引导阶段,可以注入枢轴创建的层次结构所需的内容(但不能注入任何枢轴创建的元素),第二阶段这就是您的应用程序使用的真正的注入器(可以注入任何东西)。

Specifically, since you're going to traverse a deep object structure, this really looks like a job for the Visitor pattern. Also, what you're describing seems to call out for a two-stage injector: a "bootstrap" stage that can inject stuff needed by the pivot-created hierarchy (but can't inject any pivot-created elements) and a second stage that is the real injector used by your app (that can inject anything).

我建议的基本模式是:创建一个遍历层次结构的访问者,它会对需要的东西进行注入,并记录那些需要注入到其他地方的东西。然后,当完成对所有内容的访问后,它使用 Injector.createChildInjector 制作一个新的 Injector ,可以从原始的 Injector 以及数据透视创建的层次结构中的内容。

What I would suggest is this basic pattern: make a visitor that traverses the hierarchy and, as it goes, it does injection on those things that need it and records those things that need to be injected elsewhere. Then, when it is done visitng everything, it uses Injector.createChildInjector to make a new Injector that can inject stuff from the original Injector and stuff from the pivot-created hierarchy.

首先定义一个访问者,可以在此访问所有内容层次结构:

First define a visitor that can hit everything in this hierarchy:

public interface InjectionVisitor {
  void needsInjection(Object obj);
  <T> void makeInjectable(Key<T> key, T instance);
}

然后为所有数据透视创建的元素定义一个接口:

Then define an interface for all your pivot-created elements:

public interface InjectionVisitable {
  void acceptInjectionVisitor(InjectionVisitor visitor);
}

您可以在数据透视创建的类中实现此接口为(假设 FooContainer 类中的代码):

You'd implement this interface in your pivot-created classes as (assuming this code in the FooContainer class):

public void acceptInjectionVisitor(InjectionVisitor visitor) {
  visitor.needsInjection(this);
  visitor.makeInjectable(Key.get(FooContainer.class), this);
  for (InjectionVisitable child : children) {
    child.acceptInjectionVisitor(visitor);
  }
}

请注意,前两个语句是可选的-可能是因为枢轴层次结构中的某些对象不需要注入,也可能是您以后不想注入其中的某些对象。另外,请注意 Key 的使用-这意味着,如果希望某些类可以注入特定注释,则可以执行以下操作:

Note that the first two statements are optional - it may be that some objects in the pivot hierarchy don't need injection and it could also be that some of them you wouldn't want to have injectable later. Also, notice the use of Key - this means that if you want some class to be injectable with a particular annotation you can do something like:

visitor.makeInjectable(Key.get(Foo.class, Names.named(this.getName())), this);

现在,您如何实现 InjectionVisitor ?操作方法如下:

Now, how do you implement InjectionVisitor? Here's how:

public class InjectionVisitorImpl implements InjectionVisitor {
  private static class BindRecord<T> {
    Key<T> key;
    T value;
  }

  private final List<BindRecord<?>> bindings = new ArrayList<BindRecord<?>>();
  private final Injector injector;

  public InjectionVisitorImpl(Injector injector) {
    this.injector = injector;
  }

  public void needsInjection(Object obj) {
    injector.injectMemebers(obj);
  }

  public <T> void makeInjectable(Key<T> key, T instance) {
    BindRecord<T> record = new BindRecord<T>();
    record.key = key;
    record.value = instance;
    bindings.add(record);
  }

  public Injector createFullInjector(final Module otherModules...) {
    return injector.createChildInjector(new AbstractModule() {
      protected void configure() {
        for (Module m : otherModules) { install(m); }
        for (BindRecord<?> record : bindings) { handleBinding(record); }
      }
      private <T> handleBinding(BindRecord<T> record) {
        bind(record.key).toInstance(record.value);
      }
    });
  }
}

然后在 main 方法为:

PivotHierarchyTopElement top = ...; // whatever you need to do to make that
Injector firstStageInjector = Guice.createInjector(
   // here put all the modules needed to define bindings for stuff injected into the
   // pivot hierarchy.  However, don't put anything for stuff that needs pivot
   // created things injected into it.
);
InjectionVisitorImpl visitor = new InjectionVisitorImpl(firstStageInjector);
top.acceptInjectionVisitor(visitor);
Injector fullInjector = visitor.createFullInjector(
  // here put all your other modules, including stuff that needs pivot-created things
  // injected into it.
);
RealMainClass realMain = fullInjector.getInstance(RealMainClass.class);
realMain.doWhatever();

请注意, createChildInjector 的工作方式可确保如果将任何 @Singleton 东西绑定到注入到数据透视层次结构中的东西中,您将得到由实际注入器注入的相同实例-只要 firstStageInjector 能够处理注入,fullInjector 会将注入​​委托给 firstStageInjector

Note that the way createChildInjector works ensures that if you have any @Singleton things bound in the stuff injected into the pivot hierarchy, you'll get the same instances injected by your real injector - the fullInjector will delegate injectoion to the firstStageInjector so long as the firstStageInjector is able to handle the injection.

编辑添加:一个有趣的扩展(如果您想深入研究Guice魔术)是修改 InjectionImpl ,以便它在源代码中记录名为 makeInjectable 的位置。这样,当您的代码意外地告诉访问者有关同一键的两个不同内容时,这将使您从Guice中获得更好的错误消息。为此,您需要将 StackTraceElement 添加到 BindRecord ,记录 makeInjectable 内> new RuntimeException()。getStackTrace()[1] ,然后更改 handleBinding 到:

Edited to add: An interesting extension of this (if you want to delve into deep Guice magic) is to modify InjectionImpl so that it records the place in your source code that called makeInjectable. This then lets you get better error messages out of Guice when your code accidentally tells the visitor about two different things bound to the same key. To do this, you'd want to add a StackTraceElement to BindRecord, record the result of new RuntimeException().getStackTrace()[1] inside the method makeInjectable, and then change handleBinding to:

private <T> handleBinding(BindRecord<T> record) {
  binder().withSource(record.stackTraceElem).bind(record.key).toInstance(record.value);
}

这篇关于如何使用Guice @Inject到现有对象层次结构中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆