2010-05-18 45 views
11

我有一个现有的对象层次结构,其中一些对象具有需要注入的字段。还有一些其他的对象是使用Google Guice构造的,需要注入前面描述的对象层次结构中的一些对象的引用。 Guice怎么做这样的注射?如何使用Guice @注入到现有的对象层次?

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

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

我现在要采取的方法如下所述。

对于注入已有的对象层次只让有兴趣注入的所有对象实现某些接口,如:

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)称为层次结构中的根对象,并注入所有感兴趣的对象。

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

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

回答

12

这个解决方案会工作,但我想提出一个稍微不同的一个给你。

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

我想提出的是这种基本模式:让访问者遍历层次结构,并且它会注入那些需要它的东西,并记录那些需要注入到别处的东西。然后,当它完成所有事情时,它使用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东西,在东西束缚注入枢轴层次,您将获得由您的实际注射器注射的相同实例 - 只要firstStageInjector能够处理注射,fullInjector就会将注射代表委托给firstStageInjector

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

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

哇!这是相当彻底和精心的答案。我非常感激。并感谢分享Guice魔术:) 我会尝试建议的方法。 (答案明天就会被接受。) – dragonfly 2010-05-18 13:40:41

0

你可以注入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); 
    } 
} 
+1

这个解决方案的问题是使用这种技术意味着层次结构中的所有对象都是使用Guice构造的。事实并非如此。此外,我猜'使用'MembersInjector '这里是过度的,因为如果使用Guice注入Car实例,引擎实例将被正常注入。同样通过对象树传递'Injector'(或'MembersInjector')对它来说不是一个正常的用例。 – dragonfly 2010-05-18 17:07:00

相关问题