Как к @Inject в существующее использование иерархии объектов Guice?

У меня есть существующая иерархия объектов, где некоторые объекты имеют поля, которые должны быть введены. Также существуют некоторые другие объекты, которые создаются с помощью Google Guice и должны быть введены со ссылками на некоторые объекты от ранее описанной иерархии объектов. Как я делаю такой вид инжекции с Guice?

Проблема состоит в том, что объекты от существующей иерархии не были созданы с помощью Guice и поэтому не подвергаются для введения процесса по умолчанию. Существует, конечно injector.injectMembers() метод, который может ввести в существующий экземпляр объекта, но он не работает над иерархиями объектов.

Для тех, которые задаются вопросом, почему я не могу создать упомянутое использование иерархии объектов Guice. Эта иерархия представляет объекты GUI и создается платформой GUI (Центр Apache) из декларативного описания 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) поскольку корневой объект в иерархии и всех предметах интереса введен.

Не очень хорошее решение, но сделали работу на одной стороне. С другой стороны я должен ввести объекты от этой иерархии. Я предполагаю, что это может быть сделано через реализацию пользовательского поставщика для таких объектов.

Существует ли лучшее решение моей проблемы? Возможно, существует также что-то не так с моим подходом?

11
задан dragonfly 18 May 2010 в 09:35
поделиться

2 ответа

Это решение будет работать, но я хотел бы предложить вам немного другое.

В частности, поскольку вы собираетесь обходить глубокую структуру объектов, это действительно похоже на работу для паттерна Visitor. Кроме того, то, что вы описываете, похоже, требует двухэтапного инжектора: этап "bootstrap", который может инжектировать вещи, необходимые для иерархии, созданной в pivot (но не может инжектировать элементы, созданные в pivot), и второй этап, который является настоящим инжектором, используемым вашим приложением (который может инжектировать что угодно).

Я бы предложил такой базовый шаблон: сделать посетителя, который проходит по иерархии и, по мере прохождения, выполняет инъекцию тех элементов, которые в ней нуждаются, и записывает те элементы, которые должны быть инжектированы в другом месте. Затем, когда он все посетит, он использует Injector.createChildInjector для создания нового Injector, который может инжектировать вещи из исходного Injector и вещи из иерархии, созданной поворотом.

Сначала определите посетителя, который может поразить все в этой иерархии:

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

Затем определите интерфейс для всех ваших элементов, созданных в pivot:

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

Вы реализуете этот интерфейс в ваших классах, созданных в pivot, как (предполагая этот код в классе FooContainer):

public void acceptInjectionVisitor(InjectionVisitor visitor) {
  visitor.needsInjection(this);
  visitor.makeInjectable(Key.get(FooContainer.class), this);
  for (InjectionVisitable child : children) {
    child.acceptInjectionVisitor(visitor);
  }
}

Обратите внимание, что первые два утверждения необязательны - может оказаться, что некоторые объекты в иерархии pivot не нуждаются в инъекции, а также может оказаться, что некоторые из них вы не захотите иметь инъектируемыми позже. Также обратите внимание на использование 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 вещи, связанные в вещах, инжектированных в иерархию pivot, вы получите те же экземпляры, инжектированные вашим настоящим инжектором - 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);
}
12
ответ дан 3 December 2019 в 09:19
поделиться

Вы можете использовать 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);
  }
}
0
ответ дан 3 December 2019 в 09:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: