此解决方案可行,但我想向您提出一个稍微不同的解决方案。
具体来说,由于您要遍历深层对象结构,因此这看起来确实像是访问者模式的工作。此外,您所描述的似乎需要一个两阶段注入器:一个“引导”阶段,可以注入枢轴创建的层次结构所需的东西(但不能注入任何枢轴创建的元素)和第二阶段那是您的应用使用的真正注入器(可以注入任何东西)。
我建议的是这种基本模式:创建一个遍历层次结构的访问者,然后,它对那些需要它的东西进行注入,并记录那些需要在其他地方注入的东西。然后,当它访问完所有内容后,它使用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);
}