Как @Inject в существующую иерархию объектов с помощью Guice? - PullRequest
11 голосов
/ 18 мая 2010

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

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

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

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

Есть ли лучшее решение моей проблемы? Может быть, что-то не так с моим подходом?

Ответы [ 2 ]

12 голосов
/ 18 мая 2010

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

В частности, поскольку вы собираетесь проходить глубокую структуру объекта, это действительно похоже на работу для шаблона 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);
}
0 голосов
/ 18 мая 2010

Вы можете внедрить 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);
  }
}
...