如何使用Guice @Inject到现有的对象层次结构?

我有一个现有的对象层次结构,其中一些对象具有需要注入的字段。 此外,还有一些其他对象是使用Google Guice构建的,需要注入对前面描述的对象层次结构中某些对象的引用。 我如何使用Guice进行此类注射?

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

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

我目前要采取的方法如下所述。

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

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

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

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

然后我只调用mainWindow.injectAll(injector)来获取层次结构中的根对象,并注入所有感兴趣的对象。

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

我的问题有更好的解决方案吗? 也许我的方法也有问题?

这个解决方案可行,但我想提出一个稍微不同的解决方案。

具体来说,由于您将遍历深层对象结构,因此这看起来像访问者模式的工作。 此外,您所描述的似乎要求两阶段注入器:一个“引导”阶段,可以注入透视创建的层次结构所需的东西(但不能注入任何透视创建的元素)和第二阶段这是您的应用程序使用的真正注入器(可以注入任何东西)。

我建议的是这个基本模式:让访问者遍历层次结构,并且随着它的进展,它会注入那些需要它的东西并记录那些需要注入其他地方的东西。 然后,当它完成访问所有内容时,它使用Injector.createChildInjector创建一个新的Injector ,它可以从原始Injector注入东西,并从pivot创建的层次结构中注入东西。

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

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

然后为所有pivot创建的元素定义一个接口:

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

您将在pivot创建的类中实现此接口(假设此代码在FooContainer类中):

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

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

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

现在,您如何实施InjectionVisitor ? 这是如何做:

 public class InjectionVisitorImpl implements InjectionVisitor { private static class BindRecord { Key key; T value; } private final List> bindings = new ArrayList>(); private final Injector injector; public InjectionVisitorImpl(Injector injector) { this.injector = injector; } public void needsInjection(Object obj) { injector.injectMemebers(obj); } public  void makeInjectable(Key key, T instance) { BindRecord record = new BindRecord(); 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  handleBinding(BindRecord 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事物,您将获得由真实注入器注入的相同实例 – fullInjector会将injectoion委托给firstStageInjector ,只要firstStageInjector能够处理注入。

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

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

您可以注入MembersInjectors以注入嵌套字段。 例如,这将深入注入现有的Car实例:

 public class Car { Radio radio; List seats; Engine engine; public Car(...) {...} @Inject void inject(RadioStation radioStation, MembersInjector seatInjector, MembersInjector engineInjector) { this.radio.setStation(radioStation); for (Seat seat : seats) { seatInjector.injectMembers(seat); } engineInjector.injectMembers(engine); } } public class Engine { SparkPlug sparkPlug; Turbo turbo public Engine(...) {...} @Inject void inject(SparkPlug sparkplug, MembersInjector turboInjector) { this.sparkPlug = sparkPlug; turboInjector.injectMembers(turbo); } }