Это решение будет работать, но я бы хотел предложить вам немного другое.
В частности, поскольку вы собираетесь проходить глубокую структуру объекта, это действительно похоже на работу для шаблона Visitor. Кроме того, то, что вы описываете, по-видимому, требует двухэтапного инжектора: этап «начальной загрузки», который может вводить вещи, необходимые для иерархии, созданной в сводной области (но не может вводить элементы, созданные в сводной области), и второй этап. это настоящий инжектор, используемый вашим приложением (который может вводить что угодно).
То, что я хотел бы предложить, - это базовый шаблон: создать посетителя, который пересекает иерархию и, по ходу дела, делает инъекцию в те вещи, которые в ней нуждаются, и записывает те вещи, которые должны быть внедрены в другом месте. Затем, когда это делается, посещая все, он использует 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);
}