【问题标题】:How to @Inject into existing object hierarchy using Guice?如何使用 Guice @Inject 到现有的对象层次结构中?
【发布时间】:2011-02-20 19:27:08
【问题描述】:

我有一个现有的对象层次结构,其中一些对象具有需要注入的字段。还有一些其他对象是使用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),并注入所有感兴趣的对象。

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

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

【问题讨论】:

    标签: java user-interface dependency-injection guice


    【解决方案1】:

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

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

    我建议的是这种基本模式:创建一个遍历层次结构的访问者,然后,它对那些需要它的东西进行注入,并记录那些需要在其他地方注入的东西。然后,当它访问完所有内容后,它使用Injector.createChildInjector 来创建一个新的Injector,它可以从原始Injector 中注入东西,并从枢轴创建的层次结构中注入东西。

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

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

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

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

    您将在枢轴创建的类中实现此接口为(假设此代码在 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<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 绑定在注入到枢轴层次结构中的东西中,您将获得由您的真实注入器注入的相同实例 - fullInjector 将委托向firstStageInjector 注入,只要firstStageInjector 能够处理注入。

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

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

    【讨论】:

    • 哇!这是一个非常彻底和详尽的答案。我很感激。并感谢分享 Guice 魔法 :) 我会尝试建议的方法。 (明天会接受答案。)
    【解决方案2】:

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

    public class Car {
      Radio radio;
      List<Seat> seats;
      Engine engine;
    
      public Car(...) {...}
    
      @Inject void inject(RadioStation radioStation,
          MembersInjector<Seat> seatInjector,
          MembersInjector<Engine> 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<Turbo> turboInjector) {
        this.sparkPlug = sparkPlug;
        turboInjector.injectMembers(turbo);
      }
    }
    

    【讨论】:

    • 这个解决方案的问题在于,使用这种技术意味着层次结构中的所有对象都是使用 Guice 构造的。事实并非如此。此外,我想在这里使用MembersInjector&lt;T&gt; 是多余的,因为如果使用 Guice 注入 Car 实例,引擎实例将正常注入。通过对象树传递Injector(或MembersInjector)也不是它的正常用例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多