Guice — привязать общий класс к экземпляру во время выполнения на основе определенных точек внедрения.

У меня есть класс Property<T>, который я хотел бы связать после запуска моего приложения. Property<T> представляет свойство типа T, значение которого можно изменить во время выполнения.

У меня есть классы, которые можно вводить как таковые:

public class MyClass {
    public MyClass(@Named("someName") Property<String> property) {
       ...
    }
}

Я могу связать эти экземпляры, но только после запуска моего приложения, и для этого мне нужно знать все значения аннотации Named.


Я начал искать Elements и посещать все экземпляры Element, чтобы найти все его Binding. Получив Bindings, я могу использовать InjectionPoint.forConstructor() для получения конструктора.

Меня беспокоит то, что различные типы креплений имеют свои отличия. Мне нужно обработать посещение LinkedKeyBinding, UntargettedBinding и т. д. Есть ли более простой способ получить все InjectionPoint для заданного списка модулей? Или, может быть, я иду на это неправильно?

Спасибо!


person GuiSim    schedule 21.11.2016    source источник
comment
Я думаю, что трюк будет вызывать getDependencies() для каждого Element, который вы можете, а затем регистрировать новые привязки для каждой зависимости @Annotated Property<T>, которую вы видите.   -  person Tavian Barnes    schedule 22.11.2016


Ответы (3)


Похоже, вы пытаетесь внедрить @Named(foo) Property<bar> для всех foo и bar и разрешаете их постфактум, что Guice не особенно хорошо делает. Поскольку настройка Guice происходит во время выполнения (должен запускаться метод configure), единственный способ использовать SPI Guice для того, что вы делаете, — это упреждающее связывание всех ключей, чтобы Guice не жаловался на неполный граф объектов < em>а затем проверьте это, чтобы выяснить, что нужно заполнить. Это кажется трудоемким и чрезмерно продуманным.

Кроме того, пока Guice допускает JIT или «неявные» привязки, вы не можете знать каждый ключ, который Guice может попытаться разрешить, пока объект не будет запрошен. Это может затруднить привязку всего, что вам нужно, даже если у вас есть идеальная библиотека отражения модуля или инжектора. Вы также будете обходить некоторые функции проверки графа зависимостей Guice, если не знаете, что вам нужно или что доступно до создания инжектора.

Хотя это не идеальное использование Guice и концепции внедрения зависимостей, я бы адаптировал свой код, написав PropertyOracle:

public class MyClass {
    private final Property<String> someNameProperty;

    public MyClass(PropertyOracle propertyOracle) {
        someNameProperty = propertyOracle.getString("someName");
        // The act of getting tells the oracle which properties to get. You can also
        // separate those steps without holding onto an instance:
        propertyOracle.prepare("keyToBeRequestedLater");
    }
}

Хотя этот тип обеспечения в некоторой степени дублирует Guice, в отличие от Guice, вы можете внедрить свойство с любым типом или ключом, который вы хотите, а затем разрешить его позже — возможно, асинхронно. Затем вы должны написать поддельный или фиктивный PropertyOracle для использования в тестах, что не так просто, как прямое внедрение экземпляров Property, но, возможно, это самый простой способ подключиться к запросам без осложнений Guice SPI.

person Jeff Bowman    schedule 21.11.2016

В итоге я использовал BindingTargetVisitor, который охватывает варианты использования, которые мне нужны.

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

public class InjectionPointExtractor extends DefaultBindingTargetVisitor<Object, InjectionPoint> {
  private final Predicate<TypeLiteral<?>> filter;

  public InjectionPointExtractor(Predicate<TypeLiteral<?>> filter) {
    this.filter = filter;
  }

  @Override
  public InjectionPoint visit(UntargettedBinding<?> untargettedBinding) {
    return getInjectionPointForKey(untargettedBinding.getKey());
  }

  @Override
  public InjectionPoint visit(LinkedKeyBinding<?> linkedKeyBinding) {
    return getInjectionPointForKey(linkedKeyBinding.getLinkedKey());
  }

  @Override
  public InjectionPoint visit(ProviderKeyBinding<?> providerKeyBinding) {
    return getInjectionPointForKey(providerKeyBinding.getProviderKey());
  }

  private InjectionPoint getInjectionPointForKey(Key<?> key) {
    if (filter.test(key.getTypeLiteral())) {
      return InjectionPoint.forConstructorOf(key.getTypeLiteral());
    }

    return null;
  }
}

Мы используем filter для фильтрации только классов, определенных в наших пакетах. Благодаря этому работа выполняется красиво и чисто.

Если вы не используете конструкторы @Inject, а вместо этого используете Guice для прямого задания полей, вы можете использовать TypeListener< /а>

person GuiSim    schedule 01.12.2016

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

Я бы создал пользовательскую аннотацию - скажем, Configuration с полем Property (это может быть просто значение). Я бы создал сканер пути к классу для аннотации конфигурации и зарегистрировал Provider для каждого Configuration.

Тогда это будет выглядеть так:

Ваша привязка:

public class MyClass {
    public MyClass(@Configuration("someName") Property<String> property) {
       ...
    } }

Ваш модуль:

ClasspathScanner classpathScanner = new ClasspathScanner(
    Arrays.asList("com.example"),
    Lists.newArrayList());

    List<Configuration> configurations = classpathScanner.getClasses().
        stream().
        filter(c -> find consturctors with Configuration annotation).
        collect(Collectors.toList());

    configurations.forEach(e -> bind(Key.get(String.class,e)).toProvider(new PropertyStringProvider(e.value())));

Ваш провайдер:

public class PropertyStringProvider implements Provider<String> {

  private final String propertyName;      

  public PropertyStringProvider(String propertyName) {
    this.propertyName = propertyName;
  }

  @Override
  public String get() {
    return //find property for given name. Best to use Archaius framework
  }
}

Ну, это предложение вдохновлено фреймворком Governator, который основан на guice. https://github.com/Netflix/Governator/wiki/Configuration-Mapping

person Milan Baran    schedule 21.11.2016
comment
Я не хотел использовать отражения, так как это могло привести к захвату классов, которые определены, но не связаны в Guice. Например, если я использую библиотеку, но мне нужны только некоторые из ее классов, я не хочу, чтобы мое приложение требовало от меня определения свойств для всех классов этой библиотеки, даже если я не использую их все. - person GuiSim; 01.12.2016