Ответ 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);
}